using System;
using System.Text;
using System.Data;
using System.Collections;
using System.Collections.Generic;

using Framework;
using System.Runtime.Serialization;

namespace SchemaMonitor
{
    //Table-Row Class (Customisable half)
    public partial class CNode
    {

        #region Constants
        private static string JOIN_USER = string.Concat(TABLE_NAME, " INNER JOIN ", CUser.TABLE_NAME, " ON NodeUserId=UserId"); //Nullable => Use LEFT OUTER JOIN
        private static string JOIN_TYPE = string.Concat(TABLE_NAME, " INNER JOIN ", CType.TABLE_NAME, " ON NodeTypeId=TypeId"); //Nullable => Use LEFT OUTER JOIN
        #endregion

        #region Constructors (Public)
        //Custom
        public CNode(json.CNodeRoot json, CUser u) : this(json, u.UserId) { }
        public CNode(json.CNodeRoot parsed, long? userId)
        {
            this.NodeGuid = parsed.Guid;
            this.NodeTypeId = parsed.TypeId;
            this.NodeUserId = userId??0;
            this.NodeDataAuto = parsed.Fields.Serialise();
			if (Guid.Empty.Equals(parsed.Guid))
				this.NodeGuid = this.NodeHashSha;
        }

        //Default Connection String
        public CNode() : base() {}
        public CNode(Guid nodeId) : base(nodeId) {}
        
        //Alternative Connection String
        public CNode(CDataSrc dataSrc) : base(dataSrc) {}
        public CNode(CDataSrc dataSrc, Guid nodeId) : base(dataSrc, nodeId) {}
        
        //Transactional (shares an open connection)
        protected internal CNode(CDataSrc dataSrc, long nodeId, IDbTransaction txOrNull) : base(dataSrc, nodeId, txOrNull) {}        
        #endregion

        #region Default Values
        protected override void InitValues_Custom()
        {
            //Custom default values (e.g. DateCreated column)
            _nodeCreated = DateTime.Now;
            //Member variables (e.g. for child collections)
        }
        #endregion
        
        #region Default Connection String
        protected override CDataSrc DefaultDataSrc()  { return CDataSrc.Default;  }
        #endregion

        #region Members
        //Foreign Keys
        [NonSerialized()]   private CUser _user;

        //Child Collections

        //Xml Data (as high-level objects)
        [NonSerialized()]   private json.CNodeRoot _root;

        #endregion

        #region Properties - Relationships
        //Relationships - Foriegn Keys (e.g parent)
        public CType Type { get { return CType.Cache.GetById(this.NodeTypeId); } }
        public CUser User
        {
            get
            {
                if (_user == null)
                {
                    lock (this)
                    {
                        if (_user == null)
                            _user = new CUser(this.NodeUserId);
                    }
                }
                return _user;
            }
            set
            {
                _user = value;
                _nodeUserId = null != value ? value.UserId : int.MinValue;
            }
        }
		//Relationships - Collections (e.g. children)
		public CChapterList Chapters { get { return CChapter.Cache.GetByNodeGuid(this.NodeGuid); } }
		protected CChapterList Chapters_(IDbTransaction tx) { return new CChapter(DataSrc).SelectByNodeGuid(this.NodeGuid, tx); } //Only used for cascade deletes

		public CNodeLabelList NodeLabels { get { return CNodeLabel.Cache.GetByNodeGuid(this.NodeGuid); } }
		protected CNodeLabelList NodeLabels_(IDbTransaction tx) { return new CNodeLabel(DataSrc).SelectByNodeGuid(this.NodeGuid, tx); } //Only used for cascade deletes

		public CNodeLabels_OneToOneList NodeLabels_OneToOnes { get { return CNodeLabels_OneToOne.Cache.GetByNodeGuid(this.NodeGuid); } }
		protected CNodeLabels_OneToOneList NodeLabels_OneToOnes_(IDbTransaction tx) { return new CNodeLabels_OneToOne(DataSrc).SelectByNodeGuid(this.NodeGuid, tx); } //Only used for cascade deletes

		//Relationships - 2-Step Walk
		public CMetaTag Rating { get { return NodeLabels_OneToOnes.GetByType(CTagType.Cache.FB_PostRating.TypeId); } }
		public int RatingTagId { get { return null == Rating ? 0 : Rating.TagId; } set { if (value <= 0) { if (null != Rating) Rating.Delete(); } else { if (null == Rating) { var m = new CNodeLabels_OneToOne() { LabelNodeGuid = this.NodeGuid, LabelTypeId = CTagType.Cache.FB_PostRating.TypeId, LabelTagId = value }; m.Save(); } } } }

		public CMetaTagList MetaTags { get { return NodeLabels.Metatags; } }
		public CMetaTagList RemainingMetaTags(string search) { return NodeLabels.RemainingMetatags(search); }

		public CMetaTagList MetaTags_O2O { get { return NodeLabels_OneToOnes.Metatags; } }
		public CMetaTagList RemainingMetaTags_O2O(string search) { return NodeLabels_OneToOnes.RemainingMetatags(search); }

		#endregion

		#region Properties - Customisation
		public json.CFieldPointer AsPointer(json.CNodeBasic parent, CTag t)
        {
            return new json.CFieldPointer(parent, t, this);
        }
        //Derived/ReadOnly (e.g. xml classes, presentation logic)
        public json.CNodeRoot Root
        {
            get
            {
                if (null == _root)
                    if (null != this.NodeData)
                        _root = new json.CNodeRoot(NodeDataAuto, Type);
                    else
                    {
                        _root = new json.CNodeRoot(Type);
                        NodeDataAuto = new byte[] { };
                    }
                return _root;
            }
            set
            {
                _root = value;
                NodeDataAuto = _root.Fields.Serialise();
                if (value.HasId)
                    NodeGuid = value.Guid;
            }

        }
        public byte[] NodeDataAuto
        {
            get
            {
                var bin = this.NodeData;
                if (!NodeDataIsGz)
                    return bin;
                return CBinary.Unzip(bin);
            }
            set
            {
                this.NodeData = value;
                this.NodeDataIsGz = false;
                this.NodeHashSha = CBinary.Sha128_(value);

                if (value.Length > 128)
                {
                    var gz = CBinary.Zip(value);
                    if (gz.Length * 1.05 < value.Length) //Require min 5% compression
                    {
                        this.NodeData = gz;
                        this.NodeDataIsGz = true;
                    }
                }
            }
        }
        #endregion

        #region Save/Delete Overrides
        //Can Override base.Save/Delete (e.g. Cascade deletes, or insert related records)
        public bool HasChanged
        {
            get
            {
                if (null == _root)
                    return false;
                return (_root.Hash() != this.NodeHashSha) ;
            }
        }
        public override void Save(IDbTransaction txOrNull)
        {
			if (null != _root || Guid.Empty.Equals(NodeGuid))
			{
				NodeDataAuto = Root.Fields.Serialise();
				if (_root.HasCreatedTime)
					_nodeCreated = _root.CreatedTime;
				NodeGuid = _root.Guid;
				_root = null; //reload from binary
			}

			NodeUpdated = DateTime.Now;
            try
            {
                base.Save(txOrNull);
            }
            catch
            {
                base.Update(txOrNull);
            }
        }

        public override void Delete(IDbTransaction txOrNull)
        {
			//Cascade-Delete (all child collections) 
			this.NodeLabels_(txOrNull).DeleteAll(txOrNull);
			this.NodeLabels_OneToOnes_(txOrNull).DeleteAll(txOrNull);

			DeleteById_(txOrNull);
            //base.Delete(txOrNull);
        }
        #endregion

        #region Custom Database Queries
        private readonly static string SQL_BY_TYPE_FOR_USER = string.Concat("SELECT NodeTypeId, COUNT(NodeGuid), TypeName, TypeDiscoveryId FROM ", JOIN_TYPE, " WHERE NodeUserId=@userId GROUP BY NodeTypeId, TypeName, TypeDiscoveryId ORDER BY TypeDiscoveryId, TypeName");
		[Obsolete]
		public CNameValueList CountsByTypesForUserId(long userId)
        {
            var cmd = new CCommand(SQL_BY_TYPE_FOR_USER, new CNameValueList("userId", userId), false);
            return DataSrc.MakeNameValueList(cmd);
        }
        private CNodeList SelectById_Archived(Guid g)
        {
            var where = new CCriteriaList(1);
            where.Add(PrimaryKeyName, g);
            return SelectWhere(where, CNodesArchived.TABLE_NAME);
        }

        public CNode GetById(Guid g, bool undelete = false)
        {
            //Select by PKs
            CNodeList match = SelectById(g);
            if (match.Count > 0)
                return match[0];

            match = SelectById_Archived(g);
            if (match.Count > 0)
                if (undelete)
                {
                    match[0].Undelete();
                    return GetById(g);
                }

            //No such object
            return null;
        }
        public bool Archive()
        {
            return Archive(NodeGuid) > 0;
        }
        public int Archive(Guid g)
        {
            return CDataSrc.Default.ExecuteNonQuery("SP_Delete_Node", new object[] { g }, null);
        }
        public bool Undelete()
        {
            return Undelete(NodeGuid) > 0;
        }
        public int Undelete(Guid g)
        {
            return CDataSrc.Default.ExecuteNonQuery("SP_Undelete_Node", new object[] { g }, null);
        }

        //For Stored Procs can use: MakeList (matching schema), or DataSrc.ExecuteDataset (reports etc)
        //For Dynamic sql, can use: SelectSum, SelectDistinct, SelectCount, SelectWhere (inherited methods)
        //                see also: SelectBy[FK], Search and Count (auto-generated sample queries)
        public int DeleteById_(IDbTransaction tx = null)
        {
            return DeleteById_(NodeGuid, tx);
        }
        public int DeleteById_(Guid g, IDbTransaction tx = null)
        {
            return DeleteById(this.DataSrc, g, tx);
        }
        public static int DeleteById(Guid g, IDbTransaction tx = null)
        {
            return DeleteById(CDataSrc.Default, g, tx);
        }
        public static int DeleteById(CDataSrc db, Guid g, IDbTransaction tx = null)
        {
            var p = new CNameValueList("nodeGuid", g);
            return db.ExecuteNonQuery("SP_Delete_Node", p, tx);
        }
        public static int UndeleteById(CDataSrc db, Guid g, IDbTransaction tx = null)
        {
            var p = new CNameValueList("nodeGuid", g);
            return db.ExecuteNonQuery("SP_Undelete_Node", p, tx);
        }

        public CNodeList SelectByUserIds(List<long> userIds) { return SelectWhere(new CCriteriaList("NodeUserId", ESign.IN, userIds)); }

        public CNodeList SelectByUserIdAndTypeId(                long userId, int nodeTypeId) { return SelectWhere(    new CCriteriaList(new CNameValueList("NodeTypeId", nodeTypeId, "NodeUserId", userId))); }
        public CNodeList SelectByUserIdAndTypeId(CPagingInfo pi, long userId, int nodeTypeId) { return SelectWhere(pi, new CCriteriaList(new CNameValueList("NodeTypeId", nodeTypeId, "NodeUserId", userId))); }


        public CNodeList RecentAdds(CPagingInfo pi, long userId)
        {
            return SelectByUserId(pi, userId);
        }
        public CNodeList RecentAdds(CPagingInfo pi, long userId, int typeId)
        {
            return SelectByUserIdAndTypeId(pi, userId, typeId);
        }
        public CNodeList RecentDeletes(CPagingInfo pi, long userId)
        {
            pi.TableName = CNodesArchived.TABLE_NAME;
            return SelectByUserId(pi, userId);
        }
        public CNodeList RecentDeletes(CPagingInfo pi, long userId, int typeId)
        {
            pi.TableName = CNodesArchived.TABLE_NAME;
            return SelectByUserId(pi, userId);
        }
        #endregion

        #region Cloning
        public CNode Clone(CDataSrc target, IDbTransaction txOrNull) //, int parentId)
        {
            //Shallow copy: Copies the immediate record, excluding autogenerated Pks
            CNode copy = new CNode(this, target);

            //Deep Copy - Child Entities: Cloned children must reference their cloned parent
            //copy.SampleParentId = parentId;

            copy.Save(txOrNull);

            //Deep Copy - Parent Entities: Cloned parents also clone their child collections
            //this.Children.Clone(target, txOrNull, copy.NodeId);

            return copy;
        }
        #endregion

        #region ToXml
        protected override void ToXml_Custom(System.Xml.XmlWriter w)
        {
            //Store(w, "Example", this.Example)
        }
        #endregion
    }
}